﻿using Foundation.Core;
using System.Collections.Concurrent;
using System.Linq.Expressions;

namespace Microsoft.EntityFrameworkCore
{
    public static class EnumConveterHelper
    {
        private static ConcurrentDictionary<Type, (Expression, Expression)> _shortNameMaps = new ConcurrentDictionary<Type, (Expression, Expression)>();

        public static (Expression<Func<TEnum, string>>, Expression<Func<string, TEnum>>) CreateShortNameExpression<TEnum>()
        {
            var type = typeof(TEnum);
            var actualType = type;

            var isNullable = type.IsNullableType();

            if (isNullable)
            {
                actualType = type.UnwrapNullableType();
            }

            if (!actualType.IsEnum)
            {
                throw new InvalidOperationException("This converter can only be used with enum or nullable enum types.");
            }

            var (toStrExp, toEnumExp) = _shortNameMaps.GetOrAdd(type, (t) =>
            {
                var members = EnumHelper.GetMembers(actualType);

                var enumToStrDict = new Dictionary<TEnum, string>();
                var strToEnumDict = new Dictionary<string, TEnum>(StringComparer.OrdinalIgnoreCase);

                foreach (var m in members)
                {
                    var shortName = GetShortName(m);
                    var hasShorName = !string.IsNullOrEmpty(shortName);

                    var str = hasShorName ? shortName : m.Name;

                    enumToStrDict[(TEnum)m.Value] = str;

                    if (!strToEnumDict.TryGetValue(m.Name, out var v))
                    {
                        strToEnumDict[m.Name] = (TEnum)m.Value;
                    }

                    if (hasShorName && !strToEnumDict.TryGetValue(shortName, out v))
                    {
                        strToEnumDict[shortName] = (TEnum)m.Value;
                    }
                }

                Func<TEnum, string> toStr = (v) =>
                {
                    if (isNullable && v == null) return null;

                    if (!enumToStrDict.TryGetValue(v, out var str))
                    {
                        return v.ToString();
                    }

                    return str;
                };

                Func<string, TEnum> toEnum = (v) =>
                {
                    if (isNullable && string.IsNullOrWhiteSpace(v)) return default;

                    strToEnumDict.TryGetValue(v, out var p);

                    return p;
                };

                Expression<Func<TEnum, string>> toStringExpression = v => toStr(v);
                Expression<Func<string, TEnum>> toEnumExpression = v => toEnum(v);

                return (toStringExpression, toEnumExpression);
            });

            return ((Expression<Func<TEnum, string>>)toStrExp, (Expression<Func<string, TEnum>>)toEnumExp);
        }

        public static string GetShortName(IEnumMemberInfo enumMember)
        {
            if (!enumMember.Attributes.IsNullOrEmpty())
            {
                foreach (var item in enumMember.Attributes)
                {
                    var t = item.GetType();

                    if (t.FullName.StartsWith("IMC.") && t.FullName.EndsWith("ShortNameAttribute"))
                    {
                        var p = t.GetProperty("Name");

                        if (p != null && p.PropertyType == typeof(string))
                        {
                            var name = p.GetValue(item) as string;

                            if (!string.IsNullOrWhiteSpace(name)) return name;
                        }
                    }
                }
            }

            return null;
        }
    }
}
